#include "opengl_runtime.h"

namespace PanNN {
namespace OpenGL {

OpenGLRuntime::OpenGLRuntime() {}
OpenGLRuntime::~OpenGLRuntime() {}

ErrorCode OpenGLRuntime::Init() {
    PNN_LOG(INFO) << "Rt Init Begin";
    if (!(eglGetCurrentContext() != EGL_NO_CONTEXT)) {
        m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if (m_display == EGL_NO_DISPLAY) {
            PNN_LOG(ERROR) << "eglGetDisplay error";
            return GL_RUNTIME_INIT_ERR;
        }

        int majorVersion;
        int minorVersion;
        auto glRet = eglInitialize(m_display, &majorVersion, &minorVersion);
        if (glRet != EGL_TRUE) {
            PNN_LOG(ERROR) << "eglInitialize error";
            return GL_RUNTIME_INIT_ERR;
        }

        EGLint numConfigs;
        static const EGLint configAttribs[] = {EGL_SURFACE_TYPE,
                                               EGL_PBUFFER_BIT,
                                               EGL_RENDERABLE_TYPE,
                                               EGL_OPENGL_ES2_BIT,
                                               EGL_RED_SIZE,
                                               8,
                                               EGL_GREEN_SIZE,
                                               8,
                                               EGL_BLUE_SIZE,
                                               8,
                                               EGL_ALPHA_SIZE,
                                               8,
                                               EGL_NONE};

        EGLConfig surfaceConfig;
        if (!eglChooseConfig(m_display, configAttribs, &surfaceConfig, 1, &numConfigs)) {
            eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            eglTerminate(m_display);
            m_display = EGL_NO_DISPLAY;
            PNN_LOG(ERROR) << "eglChooseConfig error";
            return GL_RUNTIME_INIT_ERR;
        }

        static const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
        m_context = eglCreateContext(m_display, surfaceConfig, NULL, contextAttribs);
        static const EGLint surfaceAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
        m_surface = eglCreatePbufferSurface(m_display, surfaceConfig, surfaceAttribs);

        glRet = eglMakeCurrent(m_display, m_surface, m_surface, m_context);
        if (glRet != EGL_TRUE) {
            PNN_LOG(ERROR) << "eglMakeCurrent error";
            return GL_RUNTIME_INIT_ERR;
        }

        eglBindAPI(EGL_OPENGL_ES_API);
        // int major;
        // glGetIntegerv(GL_MAJOR_VERSION, &major);
        // if (major < 3) {
        //     return GL_RUNTIME_INIT_ERR;
        // }
    } else {
        m_context = EGL_NO_CONTEXT;
        PNN_LOG(ERROR) << "eglGetCurrentContext() != EGL_NO_CONTEXT";
        return GL_RUNTIME_INIT_ERR;
    }
    PNN_LOG(INFO) << "Rt Init End";
    return NONE_ERR;
}

// ErrorCode OpenGLRuntime::CopyBufferToImage() { return NONE_ERR; }

// ErrorCode OpenGLRuntime::CopyImageToBuffer() { return NONE_ERR; }

// ErrorCode OpenGLRuntime::CopyHostToDeviceBuffer() { return NONE_ERR; }

// ErrorCode OpenGLRuntime::CopyDeviceBufferToHost() { return NONE_ERR; }

// ErrorCode OpenGLRuntime::CopyDeviceToHostImage() { return NONE_ERR; }

GLuint OpenGLRuntime::LoadShader(GLenum shaderType, const char *pSource) {
    GLuint shader = glCreateShader(shaderType);
    if (shader) {
        glShaderSource(shader, 1, &pSource, NULL);
        glCompileShader(shader);
        GLint compiled = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen) {
                char *buf = (char *)malloc(infoLen);
                if (buf) {
                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
                    fprintf(stderr, "Could not compile shader %d:\n%s\n", shaderType, buf);
                    free(buf);
                }
                glDeleteShader(shader);
                shader = 0;
            }
        }
    }
    return shader;
}

GLuint OpenGLRuntime::CreateComputeProgram(const char *pComputeSource) {
    GLuint computeShader = LoadShader(GL_COMPUTE_SHADER, pComputeSource);
    if (!computeShader) {
        return 0;
    }

    GLuint program = glCreateProgram();
    if (program) {
        glAttachShader(program, computeShader);
        glLinkProgram(program);
        GLint linkStatus = GL_FALSE;
        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
        if (linkStatus != GL_TRUE) {
            GLint bufLength = 0;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
            if (bufLength) {
                char *buf = (char *)malloc(bufLength);
                if (buf) {
                    glGetProgramInfoLog(program, bufLength, NULL, buf);
                    fprintf(stderr, "Could not link program:\n%s\n", buf);
                    free(buf);
                }
            }
            glDeleteProgram(program);
            program = 0;
        }
    }
    return program;
}

}  // namespace OpenGL
}  // namespace PanNN
